﻿using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    public class Image2 : System.IDisposable
    {
        //****************************************************************************************************/
        // Constants
        //****************************************************************************************************/

        //****************************************************************************************************/
        // Enumerations
        //****************************************************************************************************/

        //****************************************************************************************************/
        // Structures
        //****************************************************************************************************/

        //****************************************************************************************************/
        // Members
        //****************************************************************************************************/

        private bool _Disposed = false;

        private int _Width = 0;
        private int _Height = 0;
        private int _BitsPerPixel = 0;
        private int _BytesPerPixel = 0;
        private int _SizeInBytes = 0;
        private int _SizeInPixels = 0;
        private byte[] _R = null;
        private byte[] _G = null;
        private byte[] _B = null;
        private byte[] _RGB = null;
        private float[] _H = null;
        private float[] _S = null;
        private float[] _V = null;
        private float[] _HSV = null;

        //****************************************************************************************************/
        // Constructors / Initialization / Destructors
        //****************************************************************************************************/

        public Image2()
        {
            this.Initialize();
        }

        public void Initialize()
        {
            this.Width = 0;
            this.Height = 0;
            this.BitsPerPixel = 0;
            this.BytesPerPixel = 0;
            this.SizeInBytes = 0;
            this.SizeInPixels = 0;

            this._R = null;
            this._G = null;
            this._B = null;
            this._RGB = null;
            this._H = null;
            this._S = null;
            this._V = null;
            this._HSV = null;
        }

        ~Image2()
        {
            // Do not re-create Dispose clean-up code here.
            //   Calling Dispose(false) is optimal in terms of readability and maintainability.
            this.Dispose(false);
        }

        //****************************************************************************************************/
        // Proterties
        //****************************************************************************************************/

        public int Width
        {
            get { return (this._Width); }
            set { this._Width = value; }
        }

        public int Height
        {
            get { return (this._Height); }
            set { this._Height = value; }
        }

        public int SizeInBytes
        {
            get { return (this._SizeInBytes); }
            set { this._SizeInBytes = value; }
        }

        public int SizeInPixels
        {
            get { return (this._SizeInPixels); }
            set { this._SizeInPixels = value; }
        }

        public int BytesPerPixel
        {
            get { return (this._BytesPerPixel); }
            set { this._BytesPerPixel = value; }
        }

        public int BitsPerPixel
        {
            get { return (this._BitsPerPixel); }
            set { this._BitsPerPixel = value; }
        }

        public byte[] R
        {
            get { return (this._R); }
        }

        public byte[] G
        {
            get { return (this._G); }
        }

        public byte[] B
        {
            get { return (this._B); }
        }

        public byte[] RGB
        {
            get { return (this._RGB); }
        }

        public float[] H
        {
            get { return (this._H); }
        }

        public float[] S
        {
            get { return (this._S); }
        }

        public float[] V
        {
            get { return (this._V); }
        }

        public float[] HSV
        {
            get { return (this._HSV); }
        }

        //****************************************************************************************************/
        // Indexers
        //****************************************************************************************************/

        //****************************************************************************************************/
        // Functions
        //****************************************************************************************************/

        public bool Open(string filename, out string message)
        {
            float r, g, b;
            byte[] buffer = null;
            Color color;
            Image image = null;
            Bitmap bitmap = null;

            message = "";

            try
            {
                image = Image.FromFile(filename);

                this.BytesPerPixel = 3;
                this.BitsPerPixel = this.BytesPerPixel * 8;
                this.Width = image.Width;
                this.Height = image.Height;
                this.SizeInPixels = this.Width * this.Height;
                this.SizeInBytes = this.Width * this.Height * this.BytesPerPixel;

                try
                {
                    bitmap = new Bitmap(image);

                    try
                    {
                        buffer = new byte[this.SizeInBytes];
                        for (int y = 0, i = 0; y < this.Height; y++)
                        {
                            for (int x = 0; x < this.Width; x++, i += this.BytesPerPixel)
                            {
                                color = bitmap.GetPixel(x, y);

                                buffer[i + 0] = color.R;
                                buffer[i + 1] = color.G;
                                buffer[i + 2] = color.B;
                            }
                        }

                        //System.Runtime.InteropServices.Marshal.Copy(bitmapData.Scan0, buffer, 0, this.SizeInBytes);

                        // Asssign RGB, R, G, B
                        this._RGB = buffer;
                        this._R = new byte[this.SizeInPixels];
                        this._G = new byte[this.SizeInPixels];
                        this._B = new byte[this.SizeInPixels];
                        for (int i = 0, j = 0; i < this.SizeInPixels; i++, j += 3)
                        {
                            this._R[i] = buffer[j + 0];
                            this._G[i] = buffer[j + 1];
                            this._B[i] = buffer[j + 2];
                        }
                        // Asssign HSV, H, S, V
                        this._H = new float[this.SizeInPixels];
                        this._S = new float[this.SizeInPixels];
                        this._V = new float[this.SizeInPixels];
                        this._HSV = new float[this.SizeInBytes];
                        for (int i = 0, j = 0; i < this.SizeInPixels; i++, j += 3)
                        {
                            r = this._R[i] / 255.0F;
                            g = this._G[i] / 255.0F;
                            b = this._B[i] / 255.0F;
                            this.RgbToHsv(r, g, b, out this._H[i], out this._S[i], out this._V[i]);
                            this.HSV[j + 0] = this._H[i];
                            this.HSV[j + 1] = this._S[i];
                            this.HSV[j + 2] = this._V[i];
                        }
                    }
                    catch (System.Exception exception)
                    {
                        message
                           += System.Environment.NewLine
                           + "   - "
                           + exception.Message;
                    }

                    bitmap.Dispose();
                }
                catch (System.Exception exception)
                {
                    message
                       += System.Environment.NewLine
                       + "   - "
                       + exception.Message;
                }

                image.Dispose();
            }
            catch (System.Exception exception)
            {
                message
                   += System.Environment.NewLine
                   + "   - "
                   + exception.Message;
            }

            if (message.Length == 0)
            {
                return (true);
            }
            else
            {
                message
                   = "The following error(s) occured:"
                   + message
                   ;

                this.Close();

                return (false);
            }
        }

        /// <summary>
        /// This function is included only for reference. The copy memory methos is extremely fast
        /// but does not yet work properly
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public bool OpenFast(string filename, out string message)
        {
            float r, g, b;
            byte[] buffer = null;
            Image image = null;
            Bitmap bitmap = null;

            message = "";

            try
            {
                image = Image.FromFile(filename);

                this.BytesPerPixel = 3;
                this.BitsPerPixel = this.BytesPerPixel * 8;
                this.Width = image.Width;
                this.Height = image.Height;
                this.SizeInPixels = this.Width * this.Height;
                this.SizeInBytes = this.Width * this.Height * this.BytesPerPixel;

                try
                {
                    bitmap = new Bitmap(image);

                    try
                    {
                        BitmapData bitmapData = bitmap.LockBits
                           (
                           new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                           ImageLockMode.ReadWrite,
                           PixelFormat.Format24bppRgb
                           );

                        try
                        {
                            buffer = new byte[this.SizeInBytes];

                            System.Runtime.InteropServices.Marshal.Copy(bitmapData.Scan0, buffer, 0, this.SizeInBytes);

                            // Asssign RGB, R, G, B
                            this._RGB = buffer;
                            this._R = new byte[this.SizeInPixels];
                            this._G = new byte[this.SizeInPixels];
                            this._B = new byte[this.SizeInPixels];
                            for (int i = 0, j = 0; i < this.SizeInPixels; i++, j += 3)
                            {
                                this._R[i] = buffer[j + 0];
                                this._G[i] = buffer[j + 1];
                                this._B[i] = buffer[j + 2];
                            }
                            // Asssign HSV, H, S, V
                            this._H = new float[this.SizeInPixels];
                            this._S = new float[this.SizeInPixels];
                            this._V = new float[this.SizeInPixels];
                            this._HSV = new float[this.SizeInBytes];
                            for (int i = 0, j = 0; i < this.SizeInPixels; i++, j += 3)
                            {
                                r = this._R[i] / 255.0F;
                                g = this._G[i] / 255.0F;
                                b = this._B[i] / 255.0F;
                                this.RgbToHsv(r, g, b, out this._H[i], out this._S[i], out this._V[i]);
                                this.HSV[j + 0] = this._H[i];
                                this.HSV[j + 1] = this._S[i];
                                this.HSV[j + 2] = this._V[i];
                            }
                        }
                        catch (System.Exception exception)
                        {
                            message
                               += System.Environment.NewLine
                               + "   - "
                               + exception.Message;
                        }

                        bitmap.UnlockBits(bitmapData);
                    }
                    catch (System.Exception exception)
                    {
                        message
                           += System.Environment.NewLine
                           + "   - "
                           + exception.Message;
                    }

                    bitmap.Dispose();
                }
                catch (System.Exception exception)
                {
                    message
                       += System.Environment.NewLine
                       + "   - "
                       + exception.Message;
                }

                image.Dispose();
            }
            catch (System.Exception exception)
            {
                message
                   += System.Environment.NewLine
                   + "   - "
                   + exception.Message;
            }

            if (message.Length == 0)
            {
                return (true);
            }
            else
            {
                message
                   = "The following error(s) occured:"
                   + message
                   ;

                this.Close();

                return (false);
            }
        }

        public void Close()
        {
            this.Initialize();
        }

        public void RgbToHsv(float r, float g, float b, out float h, out float s, out float v)
        {
            float min = 0;
            float max = 0;
            float delta = 0;

            if (r < g)
            {
                if (r < b)
                    min = r;
                else
                    min = b;
            }
            else
            {
                if (g < b)
                    min = g;
                else
                    min = b;
            }

            if (r > g)
            {
                if (r > b)
                    max = r;
                else
                    max = b;
            }
            else
            {
                if (g > b)
                    max = g;
                else
                    max = b;
            }

            delta = max - min;

            if (max == 0)
            {
                h = -1.0F;
                s = -1.0F;
                v = 0.0F;
            }
            else
            {
                if (min == max)
                {
                    h = -1.0F;
                    s = 0.0F;
                    v = max;
                }
                else
                {
                    if (max == r)
                    {
                        if (g < b)
                        {
                            h = 60 * ((g - b) / delta) + 360.0F;
                        }
                        else
                        {
                            h = 60 * ((g - b) / delta) + 0.0F;
                        }
                    }
                    else if (max == g)
                    {
                        h = 60 * ((b - r) / delta) + 120.0F;
                    }
                    else if (max == b)
                    {
                        h = 60 * ((r - g) / delta) + 240.0F;
                    }
                    else
                    {
                        h = -1.0F;
                    }
                    s = delta / max;
                    v = max;
                }
            }
        }

        //****************************************************************************************************/
        // Interface Implementation: System.IDisposable
        //****************************************************************************************************/

        // Implement System.IDisposable.
        //   Do not make this method virtual.
        //   A derived class should not be able to override this method.
        public void Dispose()
        {
            this.Dispose(true);
            // This object will be cleaned up by the Dispose method.
            //   Take this object off the finalization queue and prevent finalization code for this object from executing a second time.
            System.GC.SuppressFinalize(this);
        }

        // Dispose (bool this._Disposing) executes in two distinct scenarios.
        //   If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed.
        //   If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects.
        //   Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
            if (!this._Disposed)
            {
                // If disposing equals true, dispose all managed and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources here.
                    this.Close();
                }

                // Call the appropriate methods to clean up unmanaged resources here.
                //   If disposing is false, only the following code is executed.
                //   Dispose unmanaged resources here.
            }
            this._Disposed = true;
        }
    }
}